<?php

/**
 * Created by PhpStorm.
 * User: mall
 * Date: 16/9/26
 * Time: 上午10:53
 */

namespace Fendx\Tools;

use Fendx\Exception\AccessDeniedException;
use Fendx\Logger;
use Lcobucci\JWT\Builder;
use Lcobucci\JWT\Configuration;
use Lcobucci\JWT\Signer\Hmac\Sha256;
use Lcobucci\JWT\Signer\Key\InMemory;
use Lcobucci\JWT\Token;
use Lcobucci\JWT\Validation\RequiredConstraintsViolated;
use Lcobucci\JWT\ValidationData;
use Lcobucci\JWT\Parser;

class AuthToken {

    /**
     * @var string
     */
    private string $secret;

    /**
     * @var string
     */
    private string $ip = '';

    /**
     * @return string
     */
    public function getSecret()
    {
        return $this->secret;
    }

    /**
     * @param $secret
     */
    public function setSecret($secret)
    {
        $this->secret = $secret;
    }

    /**
     * @return string
     */
    public function getIp(): string
    {
        return $this->ip;
    }

    /**
     * @param string $ip
     */
    public function setIp(string $ip): void
    {
        $this->ip = $ip;
    }


    /**
     * 配置秘钥加密
     * @return Configuration
     */
    public function getConfig()
    {
        $configuration = Configuration::forSymmetricSigner(
        // You may use any HMAC variations (256, 384, and 512)
            new Sha256(),
            // replace the value below with a key of your own!
            InMemory::base64Encoded($this->getSecret())
        // You may also override the JOSE encoder/decoder if needed by providing extra arguments here
        );
        return $configuration;
    }

    /**
     * 签发令牌
     */
    public function create(array $data, int $expireIn = 3600)
    {
        $config = $this->getConfig();
        assert($config instanceof Configuration);

        $now = time();

        $builder = $config->builder()
            // 签发人
            //->issuedBy('http://example.com')
            // 受众
            //->permittedFor('http://example.org')
            // JWT ID 编号 唯一标识
            //->identifiedBy($data['jwt_id'] ?? $data['user_id'])
            // 签发时间
            ->issuedAt($now)
            // 在1分钟后才可使用
//            ->canOnlyBeUsedAfter($now->modify('+1 minute'))
            // 过期时间1小时
            ->expiresAt($now + $expireIn);

        foreach ($data as $key => $val) {
            // 自定义uid 额外参数
            $builder->withClaim($key, $val);
        }

            // 自定义header 参数
            //->withHeader('foo', 'bar')
            // 生成token
            $token = $builder->getToken($config->signer(), $config->signingKey());

        return $token->toString();
    }

    /**
     * 验证令牌
     */
    public  function parse(string $token)
    {
        $config = $this->getConfig();
        //assert($config instanceof Configuration);

        $token = $config->parser()->parse($token);
        //assert($token instanceof Plain);

//Lcobucci\JWT\Validation\Constraint\IdentifiedBy: 验证jwt id是否匹配
//Lcobucci\JWT\Validation\Constraint\IssuedBy: 验证签发人参数是否匹配
//Lcobucci\JWT\Validation\Constraint\PermittedFor: 验证受众人参数是否匹配
//Lcobucci\JWT\Validation\Constraint\RelatedTo: 验证自定义cliam参数是否匹配
//Lcobucci\JWT\Validation\Constraint\SignedWith: 验证令牌是否已使用预期的签名者和密钥签名
//Lcobucci\JWT\Validation\Constraint\ValidAt: 验证要求iat，nbf和exp（支持余地配置）

        //验证jwt id是否匹配
        //$validate_jwt_id = new \Lcobucci\JWT\Validation\Constraint\IdentifiedBy('123');
        //$config->setValidationConstraints($validate_jwt_id);
        //验证签发人url是否正确
        //$validate_issued = new \Lcobucci\JWT\Validation\Constraint\IssuedBy('http://example.com');
        //$config->setValidationConstraints($validate_issued);
        //验证客户端url是否匹配
        //$validate_aud = new \Lcobucci\JWT\Validation\Constraint\PermittedFor('http://example.org');
        //$config->setValidationConstraints($validate_aud);

        //验证是否过期
        $timezone = new \DateTimeZone('Asia/Shanghai');
        $now = new \Lcobucci\Clock\SystemClock($timezone);
        $validate_jwt_at = new \Lcobucci\JWT\Validation\Constraint\ValidAt($now);
        $config->setValidationConstraints($validate_jwt_at);

        $validate_signed_with = new \Lcobucci\JWT\Validation\Constraint\SignedWith($config->signer(), $config->signingKey());
        $config->setValidationConstraints($validate_signed_with);

        $constraints = $config->validationConstraints();

        try {
            $config->validator()->assert($token, ...$constraints);
        } catch (RequiredConstraintsViolated $e) {
            // list of constraints violation exceptions:
            Logger::alarm($e->violations(), 'fendx', [
                'token' => $token,
                'error_msg' => $e->getMessage(),
                'ip' => $this->getIp(),
            ]);
            throw new AccessDeniedException('用户凭证过期或错误，请重新登录');
        }

        $result = $token->claims()->all();
        unset($result['iat']);
        unset($result['exp']);
        return $result;
    }
}
